#!/usr/bin/env python3
"""
比特币地址暴力穷举程序
该程序生成随机比特币私钥，检查对应地址的余额
注意：随机找到有余额地址的概率极小，这只是一个烧香拜佛的程序
作者：gA4ss
日期：2025-06-20
"""

import os
import time
import json
import random
import logging
import hashlib
import threading
import requests
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor, as_completed
from tqdm import tqdm
from bit import Key
from concurrent_log_handler import ConcurrentRotatingFileHandler

# 全局错误计数器(文件操作错误)
file_operation_errors = 0

# 创建日志目录
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'logs')
os.makedirs(log_dir, exist_ok=True)

# 为每次运行创建一个时间戳目录
run_timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
run_log_dir = os.path.join(log_dir, f"run_{run_timestamp}")
os.makedirs(run_log_dir, exist_ok=True)

print(f"创建日志目录: {run_log_dir}")

# 创建两种日志：一种记录所有检查过的地址，一种只记录有余额的地址
def setup_logger(name, log_file, level=logging.INFO):
    handler = ConcurrentRotatingFileHandler(
        log_file, maxBytes=10*1024*1024, backupCount=5, encoding="utf-8"
    )
    formatter = logging.Formatter('%(asctime)s - %(message)s')
    handler.setFormatter(formatter)
    
    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.addHandler(handler)
    
    return logger

# 创建日志记录器（使用时间戳命名）
all_addresses_logger = setup_logger("all_addresses", os.path.join(run_log_dir, f"{run_timestamp}_all_addresses.log"))
balance_logger = setup_logger("balance_found", os.path.join(run_log_dir, f"{run_timestamp}_balance_found.log"))
error_logger = setup_logger("errors", os.path.join(run_log_dir, f"{run_timestamp}_errors.log"))

# 同时在主日志目录创建指向最新日志的索引文件
def create_symlink_or_copy(source, target):
    """创建符号链接或备份文件"""
    global file_operation_errors
    
    # 确保目标目录存在
    target_dir = os.path.dirname(target)
    if not os.path.exists(target_dir):
        try:
            os.makedirs(target_dir, exist_ok=True)
        except Exception as e:
            print(f"无法创建目录 {target_dir}: {e}")
            file_operation_errors += 1
            return
    
    # 如果目标文件已存在，先删除
    if os.path.exists(target):
        try:
            os.remove(target)
        except Exception as e:
            print(f"无法删除旧文件 {target}: {e}")
            file_operation_errors += 1
            return
    
    # 创建新的链接或复制文件
    try:
        # 在Linux/Unix上创建符号链接
        os.symlink(source, target)
        print(f"创建符号链接: {source} -> {target}")
    except (OSError, AttributeError) as e:
        print(f"创建符号链接失败: {e}")
        file_operation_errors += 1
        # 在Windows上或如果symlink不可用，则创建一个指向文件路径的文本文件
        try:
            with open(target, 'w') as f:
                f.write(f"Current log file: {source}\n")
                f.write(f"Timestamp: {datetime.now().isoformat()}\n")
            print(f"创建文本索引文件: {target}")
        except Exception as e:
            print(f"创建文本索引文件失败: {e}")
            file_operation_errors += 1

# 创建最新日志的索引路径（但先不创建符号链接）
latest_all_addresses_path = os.path.join(log_dir, "latest_all_addresses.log")
latest_balance_path = os.path.join(log_dir, "latest_balance_found.log")
latest_errors_path = os.path.join(log_dir, "latest_errors.log")

# 配置控制台日志
console_logger = logging.getLogger("console")
console_logger.setLevel(logging.INFO)
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
console_logger.addHandler(console_handler)


class BitcoinAddressGenerator:
    """比特币地址生成器"""
    
    def __init__(self, key_algorithm="unique_bytes", retries=3):
        self.api_endpoints = [
            "https://blockchain.info/balance?active={address}",
            "https://api.blockcypher.com/v1/btc/main/addrs/{address}/balance",
            "https://blockstream.info/api/address/{address}"
        ]
        self.current_api = 0
        self.api_count = len(self.api_endpoints)
        self.request_count = 0
        self.last_request_time = 0
        self.min_delay = 1.0  # 每个API请求之间的最小延迟（秒）
        self.max_retries = retries  # API连接失败时的最大重试次数
        
        # 设置私钥生成算法
        self.key_algorithm = key_algorithm
        console_logger.info(f"使用私钥生成算法: {self.key_algorithm}")
        
        # 可用的私钥生成算法
        self.key_algorithms = {
            "unique_bytes": self._generate_key_unique_bytes,
            "random": self._generate_key_random,
            "pattern": self._generate_key_pattern,
            "incremental": self._generate_key_incremental,
            "word_based": self._generate_key_word_based
        }
        
        # 为增量算法设置初始值
        self._current_incremental_value = random.randint(0, 2**64 - 1)
        
        # 统计数据
        self.addresses_checked = 0
        self.addresses_with_balance = 0
        self.errors = 0
        
        self.found_addresses = []
    
    def generate_private_key(self):
        """根据选定的算法生成比特币私钥"""
        # 使用选定的算法生成私钥
        if self.key_algorithm in self.key_algorithms:
            return self.key_algorithms[self.key_algorithm]()
        else:
            console_logger.warning(f"未知的私钥算法: {self.key_algorithm}，使用默认算法")
            return self._generate_key_unique_bytes()
    
    def _generate_key_unique_bytes(self):
        """生成具有32个不同字节值的私钥"""
        # 创建一个包含0-255的列表
        byte_values = list(range(256))
        # 随机打乱这个列表
        random.shuffle(byte_values)
        # 取前32个值，确保每个字节都不相同
        unique_bytes = byte_values[:32]
        # 将字节转换为字节串
        private_key_bytes = bytes(unique_bytes)
        # 从这些字节创建私钥
        return Key.from_hex(private_key_bytes.hex())
    
    def _generate_key_random(self):
        """生成完全随机的私钥（最大熵）"""
        # 生成32个完全随机的字节
        random_bytes = bytes(random.randint(0, 255) for _ in range(32))
        return Key.from_hex(random_bytes.hex())
    
    def _generate_key_pattern(self):
        """生成具有一定模式的私钥"""
        # 使用一些模式，比如重复的字节序列然后添加一些随机性
        # 例如：前半部分使用一个模式，后半部分随机
        pattern_part = bytes([random.randint(0, 255)] * 16)  # 前16字节使用相同值
        random_part = bytes(random.randint(0, 255) for _ in range(16))  # 后16字节随机
        combined = pattern_part + random_part
        return Key.from_hex(combined.hex())
    
    def _generate_key_incremental(self):
        """生成递增的私钥，每次调用稍微增加一点值"""
        # 从当前值开始，使用大整数并递增
        self._current_incremental_value += random.randint(1, 1000)  # 增加一个随机的小增量
        # 确保不超过256位
        self._current_incremental_value %= 2**256
        
        # 转换为32个字节
        value_bytes = self._current_incremental_value.to_bytes(32, byteorder='big')
        return Key.from_hex(value_bytes.hex())
    
    def _generate_key_word_based(self):
        """基于单词生成私钥（类似助记词方法）"""
        # 这里使用一个简化方法，实际中可能使用BIP39标准
        # 创建一个随机字符串
        import hashlib
        random_phrase = str(random.random()) + str(time.time()) + str(self.addresses_checked)
        # 使用SHA256生成私钥
        hash_value = hashlib.sha256(random_phrase.encode()).digest()
        return Key.from_hex(hash_value.hex())
    
    def get_address_details(self, key):
        """从私钥获取地址详情"""
        return {
            "private_key_wif": key.to_wif(),
            "private_key_hex": key.to_hex(),
            "public_key": key.public_key.hex(),
            "address": key.address,
            "segwit_address": key.segwit_address
        }
    
    def check_balance(self, address):
        """检查地址余额（使用多个API以避免速率限制）"""
        # 确保请求不会太频繁
        current_time = time.time()
        time_since_last = current_time - self.last_request_time
        if time_since_last < self.min_delay:
            time.sleep(self.min_delay - time_since_last)
        
        # 轮流使用不同的API端点
        api_url = self.api_endpoints[self.current_api].format(address=address)
        self.current_api = (self.current_api + 1) % self.api_count
        
        # 实现重试逻辑
        retry_count = 0
        backoff_time = 1  # 指数退避的初始时间（秒）
        
        while retry_count <= self.max_retries:
            try:
                self.last_request_time = time.time()
                self.request_count += 1
                
                if retry_count > 0:
                    console_logger.info(f"第 {retry_count} 次重试检查地址: {address}")
                
                response = requests.get(api_url, timeout=10)
                
                # 根据不同API格式解析结果
                if "blockchain.info" in api_url:
                    data = response.json()
                    balance = data.get(address, {}).get('final_balance', 0)
                    return balance > 0, balance / 100000000  # 转换为BTC单位
                
                elif "blockcypher" in api_url:
                    if response.status_code == 200:
                        data = response.json()
                        balance = data.get('final_balance', 0)
                        return balance > 0, balance / 100000000  # 转换为BTC单位
                    else:
                        # 服务器错误，可能需要重试
                        if response.status_code >= 500 and retry_count < self.max_retries:
                            raise ConnectionError(f"服务器错误: {response.status_code}")
                        return False, 0
                
                elif "blockstream.info" in api_url:
                    if response.status_code == 200:
                        # 该API返回的是链上数据，我们需要解析未花费输出
                        try:
                            transactions = response.json().get("chain_stats", {})
                            funded = transactions.get("funded_txo_sum", 0)
                            spent = transactions.get("spent_txo_sum", 0)
                            balance = funded - spent
                            return balance > 0, balance / 100000000  # 转换为BTC单位
                        except json.JSONDecodeError:
                            # 如果是空地址，API可能返回空结果
                            return False, 0
                    elif response.status_code == 404:
                        # 地址未找到，表示没有交易记录
                        return False, 0
                    else:
                        error_logger.error(f"API错误: {response.status_code}, {api_url}")
                        # 服务器错误，可能需要重试
                        if response.status_code >= 500 and retry_count < self.max_retries:
                            raise ConnectionError(f"服务器错误: {response.status_code}")
                        return False, 0
                        
            except (requests.RequestException, ConnectionError, json.JSONDecodeError, Exception) as e:
                self.errors += 1
                retry_count += 1
                
                # 记录错误
                if retry_count <= self.max_retries:
                    error_logger.warning(f"检查地址 {address} 时出错 (尝试 {retry_count}/{self.max_retries}): {str(e)}")
                    # 使用指数退避策略
                    sleep_time = backoff_time * (2 ** (retry_count - 1))
                    sleep_time = min(sleep_time, 30)  # 最长等待30秒
                    console_logger.info(f"等待 {sleep_time} 秒后重试...")
                    time.sleep(sleep_time)
                    
                    # 尝试下一个API端点
                    api_url = self.api_endpoints[self.current_api].format(address=address)
                    self.current_api = (self.current_api + 1) % self.api_count
                else:
                    error_logger.error(f"检查地址 {address} 失败，已达到最大重试次数 ({self.max_retries}): {str(e)}")
                    # 如果API请求失败，我们增加延迟以避免被封禁
                    self.min_delay = min(self.min_delay * 1.5, 10)
                    return False, 0
        
        # 如果走到这里，表示所有重试都失败了
        return False, 0
    
    def process_address(self, key):
        """处理单个比特币地址"""
        try:
            # 获取地址详情
            details = self.get_address_details(key)
            address = details["address"]
            
            # 记录所有检查过的地址
            all_addresses_logger.info(f"{details['private_key_wif']},{address},{details['segwit_address']}")
            
            # 每检查20个地址显示一个样本地址（避免过多输出）
            if self.addresses_checked % 20 == 0:
                console_logger.info(f"示例地址 #{self.addresses_checked}: {address[:10]}...{address[-5:]} (私钥前8位: {details['private_key_hex'][:8]}...)")
            
            # 检查余额
            has_balance, balance = self.check_balance(address)
            
            self.addresses_checked += 1
            
            # 如果有余额，记录详细信息
            if has_balance:
                self.addresses_with_balance += 1
                timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                
                # 详细的记录信息
                detailed_info = {
                    "timestamp": timestamp,
                    "address": address,
                    "segwit_address": details['segwit_address'],
                    "private_key_wif": details['private_key_wif'],
                    "private_key_hex": details['private_key_hex'],
                    "public_key": details['public_key'],
                    "balance": balance,
                    "balance_btc": f"{balance} BTC"
                }
                
                # 控制台输出（引人注目的格式）
                balance_info = (
                    f"\n{'='*60}\n"
                    f"*** 找到有余额地址! ***\n"
                    f"{'='*60}\n"
                    f"发现时间: {timestamp}\n"
                    f"地址: {address}\n"
                    f"Segwit地址: {details['segwit_address']}\n"
                    f"私钥(WIF): {details['private_key_wif']}\n"
                    f"私钥(HEX): {details['private_key_hex']}\n"
                    f"公钥: {details['public_key']}\n"
                    f"余额: {balance} BTC\n"
                    f"{'='*60}"
                )
                
                # 记录到平衡日志
                balance_logger.critical(balance_info)
                
                # 显示在控制台（使用critical级别确保始终显示）
                console_logger.critical(balance_info)
                
                # 添加到找到的地址列表
                self.found_addresses.append(detailed_info)
                
                # 保存到单独的JSON文件，包含更详细的信息
                timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S')
                json_filename = f"found_{timestamp_str}_{address[:8]}.json"
                with open(os.path.join(run_log_dir, json_filename), 'w') as f:
                    json.dump(detailed_info, f, indent=2)
                    f.write("\n")
                
                # 额外保存一个人类可读的文本文件
                txt_filename = f"found_{timestamp_str}_{address[:8]}.txt"
                with open(os.path.join(run_log_dir, txt_filename), 'w') as f:
                    f.write(balance_info)
                
                # 更新当前运行的汇总文件
                run_summary_path = os.path.join(run_log_dir, "found_addresses_summary.csv")
                with open(run_summary_path, 'a') as f:
                    f.write(f"{timestamp},{address},{details['private_key_wif']},{balance} BTC\n")
                    
                # 同时更新总汇总文件
                global_summary_path = os.path.join(log_dir, "all_found_addresses.csv")
                with open(global_summary_path, 'a') as f:
                    f.write(f"{run_timestamp},{timestamp},{address},{details['private_key_wif']},{balance} BTC\n")
            
            return has_balance
            
        except Exception as e:
            self.errors += 1
            error_logger.error(f"处理地址时出错: {str(e)}")
            return False

    def run(self, iterations=1000, threads=4):
        """运行穷举搜索"""
        console_logger.info(f"===== 比特币地址穷举搜索 =====")
        console_logger.info(f"开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        console_logger.info(f"计划检查: {iterations} 个地址")
        console_logger.info(f"使用线程: {threads} 个")
        console_logger.info(f"日志目录: {os.path.abspath(log_dir)}")
        console_logger.info(f"状态面板: 每20个地址将显示一个示例")
        console_logger.info("=" * 40)
        
        # 显示算法信息
        console_logger.info("生成算法: 32字节不重复值的私钥")
        console_logger.info("API接口: [blockchain.info, blockcypher.com, blockstream.info]")
        console_logger.info("=" * 40)
        
        start_time = time.time()
        
        try:
            with ThreadPoolExecutor(max_workers=threads) as executor:
                futures = []
                # 使用tqdm显示进度条
                # 显示进度条
                with tqdm(total=iterations, desc="检查地址") as pbar:
                    # 提交所有任务
                    for _ in range(iterations):
                        key = self.generate_private_key()
                        future = executor.submit(self.process_address, key)
                        futures.append(future)
                    
                    # 统计变量
                    last_stats_time = time.time()
                    completed = 0
                    
                    # 处理结果
                    for future in as_completed(futures):
                        result = future.result()
                        completed += 1
                        pbar.update(1)
                        
                        # 每10秒显示一次统计信息
                        current_time = time.time()
                        if current_time - last_stats_time >= 10:
                            elapsed = current_time - start_time
                            current_hashrate = completed / elapsed if elapsed > 0 else 0
                            console_logger.info(f"进度更新: {completed}/{iterations} 完成 | 速率: {current_hashrate:.2f} 地址/秒 | API请求: {self.request_count} 次")
                            last_stats_time = current_time
        
        except KeyboardInterrupt:
            console_logger.info("程序被用户中断...")
        
        # 计算统计信息
        elapsed_time = time.time() - start_time
        hashrate = self.addresses_checked / elapsed_time if elapsed_time > 0 else 0
        
        console_logger.info("=" * 40)
        console_logger.info(f"搜索统计信息:")
        console_logger.info(f"总用时: {elapsed_time:.2f} 秒")
        console_logger.info(f"检查地址: {self.addresses_checked} 个")
        console_logger.info(f"平均速率: {hashrate:.2f} 地址/秒")
        console_logger.info(f"API请求: {self.request_count} 次")
        console_logger.info(f"API错误数量: {self.errors} 个")
        console_logger.info(f"文件操作错误数量: {file_operation_errors} 个")
        console_logger.info(f"有余额地址: {self.addresses_with_balance} 个")
        console_logger.info("=" * 40)
        
        # 显示找到的地址
        if self.found_addresses:
            console_logger.info("找到的地址:")
            for addr in self.found_addresses:
                console_logger.info(f"地址: {addr['address']}, 余额: {addr['balance']} BTC, 私钥: {addr['private_key_wif']}")
        else:
            console_logger.info("未找到任何有余额的地址。")
        
        return self.found_addresses


def save_checkpoint(generator, checkpoint_file="checkpoint.json"):
    """保存检查点，以便稍后恢复搜索"""
    checkpoint = {
        "timestamp": datetime.now().isoformat(),
        "addresses_checked": generator.addresses_checked,
        "addresses_with_balance": generator.addresses_with_balance,
        "errors": generator.errors,
        "found_addresses": generator.found_addresses
    }
    
    with open(os.path.join(log_dir, checkpoint_file), 'w') as f:
        json.dump(checkpoint, f, indent=2)


def main():
    """程序入口点"""
    import argparse

    # 创建命令行解析器
    parser = argparse.ArgumentParser(description="比特币地址穷举搜索器")
    parser.add_argument('-n', '--num', type=int, default=10,
                        help='检查的地址数量（默认: 10）')
    parser.add_argument('-t', '--threads', type=int, default=4,
                        help='线程数量（默认: 4）')
    parser.add_argument('-c', '--continuous', action='store_true',
                        help='连续运行模式，按批次无限执行直到用户中断')
    parser.add_argument('-T', '--time-limit', type=int, dest='time_limit', default=0,
                        help='按分钟运行，指定运行多少分钟后停止')
    parser.add_argument('-a', '--algorithm', type=str, default='unique_bytes',
                        help='私钥生成算法（默认: unique_bytes）')
    parser.add_argument('-r', '--retries', type=int, default=3,
                        help='API连接失败时的重试次数（默认: 3）')
    parser.add_argument('--check-address', type=str, default=None,
                        help='直接检查指定比特币地址的余额，而非随机生成地址')
    parser.add_argument('--show-algorithms', action='store_true',
                        help='显示所有可用的私钥生成算法说明')
    args = parser.parse_args()
    
    # 显示所有算法并退出
    if args.show_algorithms:
        algorithms = {
            "random": "使用Python的os.urandom生成32字节随机值作为私钥",
            "increment": "从1开始递增生成私钥（不建议用于实际搜索）",
            "unique_bytes": "生成具有唯一字节模式的私钥"
        }
        
        print("\n可用的私钥生成算法:")
        for name, desc in algorithms.items():
            print(f"  - {name}: {desc}")
        return
    
    # 日志目录和记录器已经在全局部分初始化
    
    # 设置运行模式
    if args.continuous and args.time_limit > 0:
        console_logger.warning("同时指定连续模式(-c)和时间限制(-T)，将使用时间限制模式")
        args.continuous = False
    
    # 初始化生成器    
    console_logger.info(f"\n使用算法 '{args.algorithm}' 生成私钥")
    console_logger.info(f"API重试次数设置为: {args.retries} 次")
    generator = BitcoinAddressGenerator(key_algorithm=args.algorithm, retries=args.retries)
    
    try:
        # 直接检查指定地址模式
        if args.check_address:
            address = args.check_address.strip()
            console_logger.info(f"直接检查地址模式: {address}")
            
            console_logger.info("正在检查指定地址的余额...")
            has_balance, balance = generator.check_balance(address)
            
            if has_balance:
                console_logger.info(f"\n{'='*60}\n地址有余额!\n{'='*60}")
                console_logger.info(f"地址: {address}")
                console_logger.info(f"余额: {balance} BTC")
                console_logger.info(f"{'='*60}")
                
                # 同时记录到余额日志
                balance_logger.info(f"指定地址检查: {address} | 余额: {balance} BTC")
            else:
                console_logger.info(f"地址 {address} 没有余额")
        
        # 按时间运行模式
        elif args.time_limit > 0:
            total_minutes = args.time_limit
            batch_size = args.num or 1000  # 默认批大小1000
            console_logger.info(f"时间模式，将运行 {total_minutes} 分钟，每批 {batch_size} 个地址")
            
            end_time = datetime.now() + timedelta(minutes=total_minutes)
            batch_num = 1
            
            while datetime.now() < end_time:
                console_logger.info(f"开始批次 #{batch_num}... 还剩 {(end_time - datetime.now()).total_seconds() / 60:.1f} 分钟")
                generator.run(batch_size, args.threads)
                save_checkpoint(generator)
                batch_num += 1
                # 如果还有不到一批的时间，则提前结束
                if (end_time - datetime.now()).total_seconds() < 30:  # 不到半分钟就退出
                    break
        # 连续运行模式
        elif args.continuous:
            batch_size = args.num or 1000  # 默认批大小1000
            console_logger.info(f"连续模式，将运行无限批次，每批 {batch_size} 个地址")
            
            batch_num = 1
            try:
                while True:
                    console_logger.info(f"开始批次 #{batch_num}...")
                    generator.run(batch_size, args.threads)
                    save_checkpoint(generator)
                    batch_num += 1
            except KeyboardInterrupt:
                console_logger.info("程序被用户中断...")
                save_checkpoint(generator)
                console_logger.info("检查点已保存.")
        # 标准模式
        else:
            console_logger.info(f"标准模式，将检查 {args.num} 个地址后结束")
            generator.run(args.num, args.threads)
    except Exception as e:
        console_logger.error(f"程序执行时出现未知错误: {type(e).__name__}: {e}")
        console_logger.exception("Error details:")
    
    finally:
        # 在程序结束时创建符号链接，确保日志文件已经被写入
        console_logger.info("创建日志文件符号链接...")
        create_symlink_or_copy(os.path.join(run_log_dir, f"{run_timestamp}_all_addresses.log"), latest_all_addresses_path)
        create_symlink_or_copy(os.path.join(run_log_dir, f"{run_timestamp}_balance_found.log"), latest_balance_path)
        create_symlink_or_copy(os.path.join(run_log_dir, f"{run_timestamp}_errors.log"), latest_errors_path)
        console_logger.info("日志链接创建完成")


if __name__ == "__main__":
    main()
